 /*******************************************************************************
 *
 *    Copyright (c) BaiNa Co. Ltd
 *
 *    LaputaBookReaderIII_v3.1_pad_v
 *	  PageTurnEffectManager.java
 *    PageTurnEffectManager
 *
 *    @author: DanLiu
 *    @since:  2011-1-11
 *    @version: 1.0
 *
 ******************************************************************************/

package small.hero.pageview;

import android.graphics.PointF;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

/**
 *
 */
public class PageTurnEffectManager {

    public PageTurnEffectManager() {
        mInterpolator = new LinearInterpolator();
        mDestPoint = new PointF();
        mTabPoint = new PointF();
    }

    private static final long MAX_ANIM_DURATION = 640;

    private static final int MAX_Y_IN_AUTO_ANIM = 50;

    private long mStartTime;

    private long mDuration;

    private Interpolator mInterpolator;

    private PointF mTabPoint;

    private PointF mDestPoint;

    private float mDistanceX;

    private float mDistanceY;

    private boolean mAutoCalculateY;

    private int mTotalX;

    private boolean mDrawDouble;

    /**
     * 确定是从左翻到右还是从右翻到左。
     * 这个值在计算翻页时的y坐标时使用。
     */
    private boolean mIsFromLeft;

    private boolean mIsFromUp;

    private boolean mDropBack;

    public boolean isDropBack() {
        return mDropBack;
    }

    private boolean dropBack(PointF tapPoint, byte turnPageFrom, int viewWidth, boolean drawDouble, boolean force) {
        if(force) {
            return false;
        } else {
            final boolean result;
            final int bound = drawDouble ? viewWidth / 4 : viewWidth / 2;
            final float x = tapPoint.x;
            switch (turnPageFrom) {
            case ScrollablePageContainer.FROM_UP_LEFT:
            case ScrollablePageContainer.FROM_DOWN_LEFT:
                result = !(x > bound);
                break;
            case ScrollablePageContainer.FROM_UP_RIGHT:
            case ScrollablePageContainer.FROM_DOWN_RIGHT:
                result = !(x < viewWidth - bound);
                break;
            default: result = false;
            break;
            }
            return result;
        }
    }

    public void applyAnimation(PointF tapPoint, byte turnPageFrom, boolean drawDoublePage, int viewWidth, int viewHeight, boolean force) {
        mTabPoint.x = tapPoint.x;
        mTabPoint.y = tapPoint.y;
        final PointF dest = mDestPoint;
        final boolean dropBack = dropBack(tapPoint, turnPageFrom, viewWidth, drawDoublePage, force);
        mDropBack = dropBack;
        mDrawDouble = drawDoublePage;
        mIsFromLeft = turnPageFrom == ScrollablePageContainer.FROM_DOWN_LEFT || turnPageFrom == ScrollablePageContainer.FROM_UP_LEFT;
        mIsFromUp = turnPageFrom == ScrollablePageContainer.FROM_UP_RIGHT || turnPageFrom == ScrollablePageContainer.FROM_UP_LEFT;
        if(drawDoublePage) {
            mTotalX = viewWidth;
            switch (turnPageFrom) {
            case ScrollablePageContainer.FROM_UP_RIGHT:
                if(dropBack) {
                    dest.y = 0;
                    dest.x = viewWidth;
                } else {
                    dest.x = 0;
                    dest.y = 0;
                }
                break;
            case ScrollablePageContainer.FROM_UP_LEFT:
                if(dropBack) {
                    dest.x = 0;
                    dest.y = 0;
                } else {
                    dest.x = viewWidth;
                    dest.y = 0;
                }
                break;
            case ScrollablePageContainer.FROM_DOWN_LEFT:
                if(dropBack) {
                    dest.x = 0;
                    dest.y = viewHeight;
                } else {
                    dest.x = viewWidth;
                    dest.y = viewHeight;
                }
                break;
            case ScrollablePageContainer.FROM_DOWN_RIGHT:
                if(dropBack) {
                    dest.x = viewWidth;
                    dest.y = viewHeight;
                } else {
                    dest.x = 0;
                    dest.y = viewHeight;
                }
                break;
            }
        } else {
            mTotalX = 2 * viewWidth;
            switch (turnPageFrom) {
            case ScrollablePageContainer.FROM_UP_RIGHT:
                if(dropBack) {
                    dest.x = viewWidth;
                    dest.y = 0;
                } else {
                    dest.x = -viewWidth;
                    dest.y = 0;
                }
                break;
            case ScrollablePageContainer.FROM_UP_LEFT:
                if(dropBack) {
                    dest.y = 0;
                    dest.x = 0;
                } else {
                    dest.x = 2 * viewWidth;
                    dest.y = 0;
                }
                break;
            case ScrollablePageContainer.FROM_DOWN_LEFT:
                if(dropBack) {
                    dest.x = 0;
                    dest.y = viewHeight;
                } else {
                    dest.x = 2 * viewWidth;
                    dest.y = viewHeight;
                }
                break;
            case ScrollablePageContainer.FROM_DOWN_RIGHT:
                if(dropBack) {
                    dest.x = viewWidth;
                    dest.y = viewHeight;
                } else {
                    dest.x = -viewWidth;
                    dest.y = viewHeight;
                }
                break;
            }
        }
        onPrepareAnim(drawDoublePage, viewWidth, viewHeight, force);
    }

    private void onPrepareAnim(boolean drawDoublePage, int viewWidth, int viewHeight, boolean force) {
        final float distanceY = mDestPoint.y - mTabPoint.y;
        final float distanceX = mDestPoint.x - mTabPoint.x;
        final int totalX = drawDoublePage ? viewWidth : 2 * viewWidth;
        final int totalY = viewHeight;
        final float xRate = Math.abs(distanceX) / totalX;
        final float yRate = Math.abs(distanceY) / totalY;
        final float rate = Math.max(xRate, yRate);
        mDuration = (long) (rate * MAX_ANIM_DURATION);
        mDistanceX = distanceX;
        mDistanceY = distanceY;
        mAutoCalculateY = force;
        mStartTime = System.currentTimeMillis();
    }

    public boolean animate(PointF tapPoint) {
        final long duration = mDuration;
        final long current = System.currentTimeMillis();
        final long startTime = mStartTime;

        final float normalizedTime = (float)(current - startTime) / duration;
        if(normalizedTime >= 1.0f) {
            return false;
        }
        final float rate = mInterpolator.getInterpolation(normalizedTime);
        tapPoint.x = mTabPoint.x + mDistanceX * rate;
        if(mAutoCalculateY) {
            autoCalculateY(tapPoint);
        } else {
            tapPoint.y = mTabPoint.y + mDistanceY * rate;
        }
        return true;
    }

    private void autoCalculateY(PointF tapPoint) {
        //抛物线与x轴在x轴正方向上的交点的x坐标。
        final PointF base = mTabPoint;
        final int k = mIsFromUp ? 1 : -1;
        final int totalX;
        final double a;
        final double c;
        if(mDrawDouble) {
            totalX = mTotalX;
            c = MAX_Y_IN_AUTO_ANIM / 2;
            a = -4 * c / Math.pow(totalX, 2);
            tapPoint.y = base.y + k * (float) (a * Math.pow(tapPoint.x - totalX / 2, 2) + c);
        } else {
            totalX = mTotalX / 2;
            c = MAX_Y_IN_AUTO_ANIM;
            a = - c / Math.pow(totalX, 2);
            if(mIsFromLeft) {
                tapPoint.y = base.y + k * (float) (a * Math.pow(tapPoint.x - totalX, 2) + c);
            } else {
                tapPoint.y = base.y + k * (float) (a * Math.pow(tapPoint.x, 2) + c);
            }
        }
    }
}
